package service;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaQuery;

import entity.Atividade;
import entity.Fase;
import entity.Fase.FaseRUP;
import entity.Iteracao.SituacaoIteracao;
import entity.Projeto.SituacaoProjeto;
import entity.CasoUso;
import entity.Execucao;
import entity.Iteracao;
import entity.Periodo;
import entity.Projeto;
import entity.Recurso;
import entity.Requisito;
import exception.AlmiranteException;



@Stateless
@LocalBean
public class ProjetoService {
	@PersistenceContext(unitName = "almirante")
	private EntityManager em;
	
	@EJB
	private RecursoService recursoService;
	
	@EJB
	private RequisitoService requisitoService;
	
	/**
	 * Inclui um novo projeto.
	 * @param nome
	 * @param inicio
	 * @param fim
	 * @return Projeto
	 */
	public Projeto incluirProjeto(String nome, Date inicio, Date fim, Recurso gerenteProjetos, Boolean acessoRapido) {
		Projeto projeto = new Projeto(nome, new Periodo(inicio, fim));
		if (existeProjetoComMesmoNome(nome, projeto))
			throw new AlmiranteException("Já existe projeto cadastrado com o nome '" +  nome +"'");
		projeto.setGerenteProjetos(gerenteProjetos);
		projeto.setAcessoRapido(acessoRapido);
		em.persist(projeto);
		
		return projeto;
	}
	
	public void alterarProjeto(Long idProjeto, String nome, Date inicio, Date fim, Boolean acessoRapido) {
		Projeto projeto = obterProjeto(idProjeto);
		if (existeProjetoComMesmoNome(nome, projeto))
			throw new AlmiranteException("Já existe projeto cadastrado com o nome '" +  nome +"'");
		Periodo novoPeriodo = new Periodo(inicio, fim);
		if (!novoPeriodo.equals(projeto.getPeriodo())) {
			projeto.setPeriodo(new Periodo(inicio, fim));			
		}
		projeto.setNome(nome);
		projeto.setAcessoRapido(acessoRapido);
	}
	
	private boolean existeProjetoComMesmoNome(String nome, Projeto projeto) {
		List<Projeto> projetos = em.createNamedQuery("listaProjetosPorNome", Projeto.class).setParameter("nome", nome).getResultList();
		
		for (Projeto p : projetos) {
			if (!p.equals(projeto))
				return true;
		}
		return false;
	}
	
	
	/**
	 * Obtem projeto pela pk.
	 * @param idProjeto
	 * @return Projeto
	 */
	public Projeto obterProjeto(Long idProjeto) {
		return em.find(Projeto.class, idProjeto);
	}
	
	public Projeto obterProjetoCompleto(Long idProjeto) {
		Projeto projeto = obterProjeto(idProjeto);
		for (Fase fase : projeto.getFases()) {
			if (fase.possuiIteracao()) {
				for (Iteracao iteracao : fase.getIteracoes()) {
					if (!iteracao.getAtividades().isEmpty()) {
						for (Atividade atividade: iteracao.getAtividades()) {
							atividade.getExecucoes().size();
						}
					}
				}
			}
		}
		
		return projeto;
	}
	
	public Projeto planejarFases(Long idProjeto) {
		Projeto projeto = obterProjeto(idProjeto);
		projeto.planejar();
		return projeto;
	}
	
	public Projeto adicionarIteracao(Long idProjeto, FaseRUP faseRup, Iteracao iteracao) {
		Projeto projeto = obterProjeto(idProjeto);
		projeto.adicionarIteracao(faseRup, iteracao);
		
		return projeto;
	}
	
	public Projeto removerIteracao(Long idProjeto, FaseRUP faseRup, Integer indice) {
		Projeto projeto = obterProjeto(idProjeto);
		projeto.removerIteracao(faseRup, indice);
		
		return projeto;
	}
	
	public Projeto adicionarFase(Long idProjeto, FaseRUP faseRup) {
		Projeto projeto = obterProjeto(idProjeto);
		projeto.adicionarFase(faseRup);
		
		return projeto;
	}
	
	public Projeto removerFase(Long idProjeto, FaseRUP faseRup) {
		Projeto projeto = obterProjeto(idProjeto);
		projeto.removerFase(faseRup);
		
		return projeto;
	}
	
	public List<Projeto> listarProjetos() {
		CriteriaQuery<Projeto> criteria = em.getCriteriaBuilder().createQuery(Projeto.class);
		
		return em.createQuery(criteria.select(criteria.from(Projeto.class))).getResultList();
	}
	
	public List<Projeto> listarProjetosPorRecurso(Integer idRecurso){
		Query query = em.createNamedQuery("listaProjetosPorRecurso");
		query.setParameter("idRecurso", idRecurso);
		List<Projeto> resultList = query.getResultList();
		/*//Faz join com a tabela atividade, quando insere projeto não possui atividade e não retorna na lista.
		//TODO Alterar o join do método para não usar duas query e essa gambi aqui...
		Query query2 = em.createNamedQuery("listaProjetosPorGerente");
		query2.setParameter("idRecurso", idRecurso);
		List<Projeto> resultListGerente = query2.getResultList();
		
		for (Projeto projeto : resultListGerente) {
			if (!resultList.contains(projeto)){
				resultList.add(projeto);
			}
		}*/
		
		return resultList;
	}
	
	public void alterarIteracao(Long idIteracao, String nome, String detalhes, Integer duracao, Integer esforco) {
		Iteracao iteracao = obterIteracao(idIteracao);
		iteracao.setNome(nome);
		iteracao.setDetalhes(detalhes);
		iteracao.setHorasEstimadas(esforco);
		if (!iteracao.getDuracao().equals(duracao)) {
			iteracao.setDuracao(duracao);			
		}
	}
	
	
	public Projeto finalizarPlanejamentoFases(Long idProjeto) {
		Projeto projeto = obterProjeto(idProjeto);
		projeto.finalizarPlanejamento();
		
		return projeto;
	}

	public Iteracao obterIteracaoComAtividadesERecursos(Long idIteracao) {
		Iteracao iteracao = obterIteracao(idIteracao);
		if (!iteracao.getAtividades().isEmpty()) {
			// para carregar as execucoes
			for (Atividade a : iteracao.getAtividades()) {
				a.getExecucoes().size();
			}
		}
		iteracao.getRecursos().size();
		
		return iteracao;
	}
	
	public Iteracao obterIteracao(Long idIteracao) {
		return em.find(Iteracao.class, idIteracao);
	}
	
	public Iteracao planejarIteracao(Long idIteracao) {
		Iteracao iteracao = obterIteracaoComAtividadesERecursos(idIteracao);
		iteracao.planejar();
		
		return iteracao;
	}

	
	public List<Atividade> listarAtividadesDaIteracao(Long idIteracao) {
		String jpql = "SELECT a " +
				"FROM Atividade a " +
				"JOIN a.iteracao i " +
				"WHERE i.id = :idIteracao " +
				"ORDER BY a.numero ";
		
		return em.createQuery(jpql, Atividade.class).
				setParameter("idIteracao", idIteracao).getResultList();
	}
	
	public Atividade criarAtividadeNaIteracao(Long idIteracao) {
		Iteracao iteracao = obterIteracaoComAtividadesERecursos(idIteracao);
		
		return iteracao.criarAtividade();
	}
	
	public void removerAtividade(Long idAtividade) {
		Atividade atividade = obterAtividade(idAtividade);
		atividade.getIteracao().removerAtividade(atividade.getNumero());
	}
	
	public void alterarAtividade(Atividade atividade) {
		em.merge(atividade);
	}
	
	public Atividade obterAtividade(Long idAtividade) {
		return em.find(Atividade.class, idAtividade);
	}
	
	/**
	 * Obtem atividade com relacionamentos carregados.
	 * @param idAtividade
	 * @return atividade com relacionamentos OneToMany
	 */
	public Atividade obterAtividadeCompleta(Long idAtividade) {
		Atividade atividade = obterAtividade(idAtividade);
		
		// Carregamento lazy das colecoes
		atividade.getIteracao().getRecursos().size();
		atividade.getRequisitos().size();
		atividade.getAtividadesPredecessoras().size();
		atividade.getAtividadesSucessoras().size();
		
		return atividade;
	}
	
	public Atividade obterAtividadeComPredecessoras(Long idAtividade) {
		Atividade atividade = obterAtividade(idAtividade);
		// Carregamento lazy das colecoes
		atividade.getAtividadesPredecessoras().size();
		return atividade;
	}
	
	public void adicionarRecursoAIteracao(Long idIteracao, Integer idRecurso) {
		Iteracao iteracao = obterIteracaoComAtividadesERecursos(idIteracao);
		Recurso recurso = recursoService.obterRecursoPorId(idRecurso);
		
		iteracao.adicionarRecurso(recurso);
	}
	
	public void removerRecursoDaIteracao(Long idIteracao, Integer idRecurso) {
		Iteracao iteracao = obterIteracao(idIteracao);
		Recurso recurso = recursoService.obterRecursoPorId(idRecurso);
		iteracao.removerRecurso(recurso);
	}
	
	public void adicionarRequisitoAIteracao(Long idRequisito, Long idIteracao) {
		Requisito requisito = requisitoService.obterRequisito(idRequisito);
		Iteracao iteracao = obterIteracao(idIteracao);
		iteracao.adicionarRequisito(requisito);
	}
	
	public void adicionarRequisitoAAtividade(Long idRequisito, Long idAtividade) {
		Requisito requisito = requisitoService.obterRequisito(idRequisito);
		Atividade atividade = obterAtividade(idAtividade);
		atividade.adicionarRequisito(requisito);
	}
	
	public void removerRequisitoDaIteracao(Long idRequisito, Long idIteracao) {
		Requisito requisito = requisitoService.obterRequisito(idRequisito);
		Iteracao iteracao = obterIteracao(idIteracao);
		iteracao.removerRequisito(requisito);
	}
	
	public void removerRequisitoDaAtividade(Long idRequisito, Long idAtividade) {
		Requisito requisito = requisitoService.obterRequisito(idRequisito);
		Atividade atividade = obterAtividade(idAtividade);
		atividade.removerRequisito(requisito);
	}
	
	public List<Atividade> listarAtividadesDaIteracaoNaoVinculadasA(Long idAtividade) {
		String jpql = "SELECT a " +
				"FROM Atividade a, Atividade a2 " +
				"WHERE a NOT MEMBER OF a2.atividadesPredecessoras " +
				"AND a2 NOT MEMBER OF a.atividadesPredecessoras " +
				"AND a2.id = :idAtividade " +
				"AND a.iteracao = a2.iteracao " +
				"AND a.id <> :idAtividade " +
				"ORDER BY a.numero";
		
		return em.createQuery(jpql, Atividade.class)
				.setParameter("idAtividade", idAtividade).getResultList();
	}
	
	public List<Atividade> listarAtividadesPredecessorasA(Long idAtividade) {
		String jpql = "SELECT a " +
				"FROM Atividade a, Atividade a2 " +
				"WHERE a MEMBER OF a2.atividadesPredecessoras " +
				"AND a2.id = :idAtividade " +
				"ORDER BY a.numero";
		
		return em.createQuery(jpql, Atividade.class)
				.setParameter("idAtividade", idAtividade).getResultList();
	}
	
	public void adicionarAtividadePredecessora(Long idAtividade, Long idPredecessora) {
		Atividade atividade = obterAtividade(idAtividade);
		Atividade predecessora = obterAtividade(idPredecessora);
		atividade.adicionarPredecessora(predecessora);
	}
	
	public void removerAtividadePredecessora(Long idAtividade, Long idPredecessora) {
		Atividade atividade = obterAtividade(idAtividade);
		Atividade predecessora = obterAtividade(idPredecessora);
		atividade.removerPredecessora(predecessora);
	}
	
	public void concluirPlanejamentoIteracao(Long idIteracao) {
		Iteracao iteracao = obterIteracao(idIteracao);
		if (iteracao.getSituacao().equals(SituacaoIteracao.EM_PLANEJAMENTO)) {
			iteracao.concluirPlanejamento();			
		} else {
			iteracao.concluirReplanejamento();
		}
	}
	
	public Iteracao planejarProximaIteracao(Long idProjeto) {
		Projeto projeto = obterProjeto(idProjeto);
		Iteracao iteracao = projeto.proximaIteracaoNaoPlanejada();
		iteracao.planejar();
		iteracao.getAtividades().size(); // carregamento lazy
		return iteracao;
	}
	
	public void iniciarIteracao(Long idIteracao, Date dataInicio) {
		Iteracao iteracao = obterIteracao(idIteracao);
		iteracao.iniciar(dataInicio);
	}
	
	public List<Atividade> listarAtividadesDoRequisitoNaIteracao(Long idRequisito, Long idIteracao) {
		String jpql = "SELECT a " +
				"FROM Atividade a, Requisito r, Iteracao i " +
				"WHERE r MEMBER OF a.requisitos " +
				"AND a MEMBER OF i.atividades " +
				"AND r.id = :idRequisito " +
				"AND i.id = :idIteracao " +
				"ORDER BY a.numero";
		
		return em.createQuery(jpql, Atividade.class)
				.setParameter("idRequisito", idRequisito)
				.setParameter("idIteracao", idIteracao).getResultList();
	}
	
	public void salvarOutrosRecursosIteracao(Long idIteracao, String outrosRecursos) {
		Iteracao iteracao = obterIteracao(idIteracao);
		iteracao.setOutrosRecursos(outrosRecursos);
	}
	
	public List<Atividade> listarAtividadesDoRecurso(Integer idRecurso, Long idProjeto) {
		String jpql = "SELECT DISTINCT a " +
				"FROM Atividade a, Iteracao i " +
				"LEFT JOIN FETCH a.execucoes " +
				"WHERE a MEMBER OF i.atividades " +
				"AND i.situacao = :situacaoIteracao " +
				"AND i.fase.projeto.id = :idProjeto " +
				"AND a.responsavel.id = :idRecurso " +
				"ORDER BY a.numero";
		
		return em.createQuery(jpql, Atividade.class)
				.setParameter("idRecurso", idRecurso)
				.setParameter("idProjeto", idProjeto)
				.setParameter("situacaoIteracao", SituacaoIteracao.EM_ANDAMENTO).getResultList();
	}
	
	public Atividade obterAtividadeComExecucoes(Long idAtividade) {
		Atividade atividade = obterAtividade(idAtividade);
		if (atividade != null) {
			atividade.getExecucoes().size();			
		}
		return atividade;
	}
	
	public void adicionarExecucaoAAtividade(Long idAtividade, Periodo periodo, Integer qtdHoras, String descricao) {
		Atividade atividade = obterAtividade(idAtividade);
		atividade.adicionarExecucao(periodo, qtdHoras, descricao);
	}
	
	public Execucao obterExecucao(Long idExecucao) {
		return em.find(Execucao.class, idExecucao);
	}
	
	public void alterarExecucao(Long idExecucao, Periodo periodo, Integer qtdHoras, String descricao) {
		Execucao execucao = obterExecucao(idExecucao);
		execucao.setPeriodo(periodo);
		execucao.setQtdHoras(qtdHoras);
		execucao.setDescricao(descricao);
	}
	
	public void excluirExecucao(Long idExecucao) {
		em.remove(obterExecucao(idExecucao));
	}
	
	public Iteracao obterIteracaoEmAndamento(Long idProjeto) {
		String jpql = "from Iteracao i " +
				"where i.fase.projeto.id = :idProjeto " +
				"and i.situacao = :situacaoIteracao ";
		try {
			Iteracao iteracao = em.createQuery(jpql, Iteracao.class)
					.setParameter("idProjeto", idProjeto)
					.setParameter("situacaoIteracao", SituacaoIteracao.EM_ANDAMENTO)
					.setMaxResults(1).getSingleResult();
			return iteracao;
		} catch (NoResultException nrEx) {
			return null;
		}
	}
	
	public List<Execucao> listarExecucoesDaAtividade(Long idAtividade) {
		String jpql = "from Execucao e " +
				"where e.atividade.id = :idAtividade " +
				"order by e.dataCriacao ";
		
		return em.createQuery(jpql, Execucao.class)
				.setParameter("idAtividade", idAtividade)
				.getResultList();
	}
	
	public List<Execucao> listarExecucoesDaIteracao(Long idIteracao) {
		String jpql = "from Execucao e " +
				"where e.atividade.iteracao.id = :idIteracao " +
				"order by e.atividade.id";
		
		return em.createQuery(jpql, Execucao.class)
				.setParameter("idIteracao", idIteracao)
				.getResultList();
	}
	
	public void concluirAtividade(Long idAtividade) {
		Atividade atividade = obterAtividade(idAtividade);
		atividade.finalizar();
	}
	
	public void cancelarAtividade(Long idAtividade) {
		Atividade atividade = obterAtividade(idAtividade);
		atividade.cancelar();
	}
	
	public void concluirIteracao(Long idIteracao, Date dataConclusao) {
		Iteracao iteracao = obterIteracao(idIteracao);
		iteracao.concluir(dataConclusao);
	}
	
	public Iteracao obterIteracaoCorrente(Long idProjeto) {
		Projeto projeto = obterProjeto(idProjeto);
		return projeto.iteracaoCorrente();
	}
	
	public Iteracao replanejarIteracao(Long idIteracao) {
		Iteracao iteracao = obterIteracaoComAtividadesERecursos(idIteracao);
		iteracao.replanejar();
		return iteracao;
	}
	
	public List<Projeto> listarProjetosDoGerente(Integer idRecurso) {
		String jpql = "from Projeto p " +
				"where p.gerenteProjetos.idRecurso = :idRecurso ";
		
		return em.createQuery(jpql, Projeto.class)
				.setParameter("idRecurso", idRecurso)
				.getResultList();
	}
	
	public List<Projeto> listarProjetosDoGerente(Integer idRecurso, String nome, SituacaoProjeto... situacoes) {
		StringBuilder jpql = new StringBuilder()
			.append("from Projeto p ")
			.append("where p.gerenteProjetos.idRecurso = :idRecurso ");
		
		if (nome != null && !nome.isEmpty())
			jpql.append("and p.nome like :nome ");
		if (situacoes != null && situacoes.length > 0)
			jpql.append("and p.situacao in :situacoes");
		
		TypedQuery<Projeto> query = em.createQuery(jpql.toString(), Projeto.class)
				.setParameter("idRecurso", idRecurso);
		if (nome != null && !nome.isEmpty())
			query.setParameter("nome", "%" + nome + "%");
		if (situacoes != null && situacoes.length > 0)
			query.setParameter("situacoes", Arrays.asList(situacoes));
		
		return query.getResultList();
	}
	
	public void excluirProjeto(Long idProjeto) {
		Projeto projeto = obterProjeto(idProjeto);
		if (!projeto.getIteracoes().isEmpty())
			throw new AlmiranteException("Projeto '" + projeto.getNome() +
					"' não pode ser excluído, pois já possui iteração cadastrada.");
		em.remove(projeto);
	}
	
	public void cancelarProjeto(Long idProjeto, String motivoCancelamento) {
		Projeto projeto = obterProjeto(idProjeto);
		projeto.cancelar(motivoCancelamento);
	}
	
	public void adicionarCasoUsoAIteracao(Integer idCasoUso, Long idIteracao) {
		CasoUso casoUso = requisitoService.obterCasoUso(idCasoUso);
		Iteracao iteracao = obterIteracao(idIteracao);
		iteracao.adicionarCasoUso(casoUso);
	}
	
	public void removerCasoUsoDaIteracao(Integer idCasoUso, Long idIteracao) {
		CasoUso casoUso = requisitoService.obterCasoUso(idCasoUso);
		Iteracao iteracao = obterIteracao(idIteracao);
		iteracao.removerCasoUso(casoUso);
	}
	
	public void adicionarCasoUsoAAtividade(Integer idCasoUso, Long idAtividade) {
		CasoUso casoUso = requisitoService.obterCasoUso(idCasoUso);
		Atividade atividade = obterAtividade(idAtividade);
		atividade.adicionarCasoUso(casoUso);
	}

	public void removerCasoUsoDaAtividade(Integer idCasoUso, Long idAtividade) {
		CasoUso casoUso = requisitoService.obterCasoUso(idCasoUso);
		Atividade atividade = obterAtividade(idAtividade);
		atividade.removerCasoUso(casoUso);
	}
	
	public void adicionarRecursoAoProjeto(Integer idRecurso, Long idProjeto) {
		Recurso recurso = recursoService.obterRecursoPorId(idRecurso);
		Projeto projeto = obterProjeto(idProjeto);
		projeto.adicionarRecurso(recurso);
	}
	
	public void removerRecursoDoProjeto(Integer idRecurso, Long idProjeto) {
		Recurso recurso = recursoService.obterRecursoPorId(idRecurso);
		Projeto projeto = obterProjeto(idProjeto);
		projeto.removerRecurso(recurso);
	}
}
